/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import java.lang.ref.WeakReference;
import java.util.Hashtable;
import java.util.ArrayList;
import java.io.Reader;
import java.io.Writer;
import java.io.IOException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import javax.swing.text.Element;
import javax.swing.text.AttributeSet;
import javax.swing.text.AbstractDocument;
import javax.swing.text.StyleConstants;
import javax.swing.event.EventListenerList;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CompoundEdit;
/**
* Document implementation
*
* @author Miloslav Metelka
* @version 1.00
*/
public class BaseDocument extends AbstractDocument implements SettingsChangeListener {
/** Registry identification property */
public static final String ID_PROP = "id"; // NOI18N
/** Line separator property for reading files in */
public static final String READ_LINE_SEPARATOR_PROP
= DefaultEditorKit.EndOfLineStringProperty;
/** Line separator property for reading files in */
public static final String WRITE_LINE_SEPARATOR_PROP
= "write-line-separator"; // NOI18N
/** File name property */
public static final String FILE_NAME_PROP = "file-name"; // NOI18N
/** Wrap search mark property */
public static final String WRAP_SEARCH_MARK_PROP = "wrap-search-mark"; // NOI18N
/** Undo manager property. This can be used to implement undo
* in a simple way. Default undo and redo actions try to get this
* property and perform undo and redo through it.
*/
public static final String UNDO_MANAGER_PROP = "undo-manager"; // NOI18N
/** Kit class property. This can become useful for getting
* the settings that logicaly belonging to the document.
*/
public static final String KIT_CLASS_PROP = "kit-class"; // NOI18N
/** String forward finder property */
public static final String STRING_FINDER_PROP = "string-finder"; // NOI18N
/** String backward finder property */
public static final String STRING_BWD_FINDER_PROP = "string-bwd-finder"; // NOI18N
/** Highlight search finder property. */
public static final String BLOCKS_FINDER_PROP = "blocks-finder"; // NOI18N
/** Maximum line width encountered during the initial read operation.
* This is filled by Analyzer and used by UI to set the correct initial width
* of the component.
* Values: java.lang.Integer
*/
public static final String LINE_LIMIT_PROP = "line-limit"; // NOI18N
/** Size of the line batch. Line batch can be used at various places
* especially when processing lines by syntax scanner.
*/
public static final String LINE_BATCH_SIZE = "line-batch-size"; // NOI18N
/** Line separator is marked by CR (Macintosh) */
public static final String LS_CR = "\r"; // NOI18N
/** Line separator is marked by LF (Unix) */
public static final String LS_LF = "\n"; // NOI18N
/** Line separator is marked by CR and LF (Windows) */
public static final String LS_CRLF = "\r\n"; // NOI18N
// special signature for change event for layer addition
static final char[] LAYER_ADDED = new char[0];
/** Maximum of concurrent read threads (other will wait until
* one of these will leave).
*/
private static final int MAX_READ_THREADS = 10;
/** Write lock without write lock */
private static final String WRITE_LOCK_MISSING
= "extWriteUnlock() without extWriteLock()"; // NOI18N
private static final boolean debugEditor = Boolean.getBoolean("netbeans.debug.editor"); // NOI18N
/** Document operations support class for this document. It presents
* the base synchronization level for most of the operations.
* Some of the operations are available through <tt>Utilities</tt> class.
*/
DocOp op;
/** How many times current writer requested writing */
private int writeDeep;
/** How many times atomic writer requested writing */
private int atomicDepth;
/* Was the document initialized by reading? */
protected boolean inited;
/* Was the document modified by doing inert/remove */
protected boolean modified;
/** Listener list */
protected EventListenerList listenerList = new EventListenerList();
/** Listener to changes in find support */
PropertyChangeListener findSupportListener;
/** Default element - lazily inited */
protected BaseElement defaultRootElem;
private SyntaxSupport syntaxSupport;
/** Layer list for document level layers */
private DrawLayerList drawLayerList = new DrawLayerList();
/** Chain of document level bookmarks */
private MarkChain bookmarkChain;
/** Reset merging next created undoable edit to the last one. */
protected boolean undoMergeReset;
/** Kit class stored here */
Class kitClass;
/** Undo event for atomic events is fired after the successful
* atomic operation is finished. The changes are stored in this variable
* during the atomic operation. If the operation is broken, these edits
* are used to restore previous state.
*/
private CompoundEdit atomicEdits;
private Acceptor identifierAcceptor;
private Acceptor whitespaceAcceptor;
/**
* @associates Syntax
*/
private ArrayList syntaxList = new ArrayList();
/** List of the positions used by storePosition()
* @associates Mark*/
private ArrayList posList = new ArrayList();
/** List of the integers marking the free positions in the posList.
* @associates Integer*/
private ArrayList posFreeList = new ArrayList();
/** Root element of line elements representation */
protected LineRootElement lineRootElement;
private LeafElement composedTextElement;
/** Create base document with a specified syntax.
* @param kitClass class used to initialize this document with proper settings
* category based on the editor kit for which this document is created
* @param syntax syntax scanner to use with this document
*/
public BaseDocument(Class kitClass, boolean addToRegistry) {
this(kitClass, addToRegistry, new DocOp());
}
private BaseDocument(Class kitClass, boolean addToRegistry, DocOp op) {
super(op);
this.op = op;
this.kitClass = kitClass;
settingsChange(null); // initialize variables from settings
op.setDocument(this);
// Line separators default to platform ones
putProperty(READ_LINE_SEPARATOR_PROP, Analyzer.getPlatformLS());
putProperty(WRITE_LINE_SEPARATOR_PROP, Analyzer.getPlatformLS());
bookmarkChain = new MarkChain(this, DrawLayerFactory.BOOKMARK_LAYER_NAME);
// Add document draw-layers
addLayer(new DrawLayerFactory.SyntaxLayer());
addLayer(new DrawLayerFactory.HighlightSearchLayer());
addLayer(new DrawLayerFactory.BookmarkLayer());
// Possibly add the document to registry
if (addToRegistry) {
Registry.addDocument(this); // add if created thru the kit
}
// Start listen on find-support
findSupportListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
findSupportChange(evt);
}
};
FindSupport.getFindSupport().addPropertyChangeListener(findSupportListener);
findSupportChange(null); // update doc by find settings
}
private void findSupportChange(PropertyChangeEvent evt) {
// set all finders to null
putProperty(STRING_FINDER_PROP, null);
putProperty(STRING_BWD_FINDER_PROP, null);
putProperty(BLOCKS_FINDER_PROP, null);
DrawLayerFactory.HighlightSearchLayer hsl
= (DrawLayerFactory.HighlightSearchLayer)findLayer(
DrawLayerFactory.HIGHLIGHT_SEARCH_LAYER_NAME);
Boolean b = (Boolean)FindSupport.getFindSupport().getPropertyNoInit(
Settings.FIND_HIGHLIGHT_SEARCH);
hsl.setEnabled((b != null) ? b.booleanValue() : false);
fireChangedUpdate(createDocumentEvent(0, getLength(),
DocumentEvent.EventType.CHANGE)); // refresh whole document
}
/** Called when settings were changed. The method is called
* also in constructor, so the code must count with the evt being null.
*/
public void settingsChange(SettingsChangeEvent evt) {
String settingName = (evt != null) ? evt.getSettingName() : null;
if (settingName == null || Settings.TAB_SIZE.equals(settingName)) {
putProperty(Settings.TAB_SIZE, SettingsUtil.getValue(kitClass,
Settings.TAB_SIZE, DefaultSettings.defaultTabSize));
}
// these should not make any visual difference
if (settingName == null || Settings.READ_BUFFER_SIZE.equals(settingName)) {
putProperty(Settings.READ_BUFFER_SIZE, SettingsUtil.getValue(kitClass,
Settings.READ_BUFFER_SIZE, DefaultSettings.defaultReadBufferSize));
}
if (settingName == null || Settings.WRITE_BUFFER_SIZE.equals(settingName)) {
putProperty(Settings.WRITE_BUFFER_SIZE, SettingsUtil.getValue(kitClass,
Settings.WRITE_BUFFER_SIZE, DefaultSettings.defaultWriteBufferSize));
}
if (settingName == null || Settings.MARK_DISTANCE.equals(settingName)) {
putProperty(Settings.MARK_DISTANCE, SettingsUtil.getValue(kitClass,
Settings.MARK_DISTANCE, DefaultSettings.defaultMarkDistance));
}
if (settingName == null || Settings.MAX_MARK_DISTANCE.equals(settingName)) {
putProperty(Settings.MAX_MARK_DISTANCE, SettingsUtil.getValue(kitClass,
Settings.MAX_MARK_DISTANCE, DefaultSettings.defaultMaxMarkDistance));
}
if (settingName == null || Settings.MIN_MARK_DISTANCE.equals(settingName)) {
putProperty(Settings.MIN_MARK_DISTANCE, SettingsUtil.getValue(kitClass,
Settings.MIN_MARK_DISTANCE, DefaultSettings.defaultMinMarkDistance));
}
if (settingName == null || Settings.READ_MARK_DISTANCE.equals(settingName)) {
putProperty(Settings.READ_MARK_DISTANCE, SettingsUtil.getValue(kitClass,
Settings.READ_MARK_DISTANCE, DefaultSettings.defaultReadMarkDistance));
}
if (settingName == null || Settings.SYNTAX_UPDATE_BATCH_SIZE.equals(settingName)) {
putProperty(Settings.SYNTAX_UPDATE_BATCH_SIZE, SettingsUtil.getValue(kitClass,
Settings.SYNTAX_UPDATE_BATCH_SIZE, DefaultSettings.defaultSyntaxUpdateBatchSize));
}
if (settingName == null || Settings.LINE_BATCH_SIZE.equals(settingName)) {
putProperty(Settings.LINE_BATCH_SIZE, SettingsUtil.getValue(kitClass,
Settings.LINE_BATCH_SIZE, DefaultSettings.defaultLineBatchSize));
}
if (settingName == null || Settings.IDENTIFIER_ACCEPTOR.equals(settingName)) {
identifierAcceptor = SettingsUtil.getAcceptor(kitClass,
Settings.IDENTIFIER_ACCEPTOR, AcceptorFactory.LETTER_DIGIT);
}
if (settingName == null || Settings.WHITESPACE_ACCEPTOR.equals(settingName)) {
whitespaceAcceptor = SettingsUtil.getAcceptor(kitClass,
Settings.WHITESPACE_ACCEPTOR, AcceptorFactory.WHITESPACE);
}
boolean stopOnEOL = SettingsUtil.getBoolean(kitClass,
Settings.WORD_MOVE_NEW_LINE_STOP, true);
if (settingName == null || Settings.NEXT_WORD_FINDER.equals(settingName)) {
putProperty(Settings.NEXT_WORD_FINDER,
SettingsUtil.getValue(kitClass, Settings.NEXT_WORD_FINDER,
new FinderFactory.NextWordFwdFinder(this, stopOnEOL, false)));
}
if (settingName == null || Settings.PREVIOUS_WORD_FINDER.equals(settingName)) {
putProperty(Settings.PREVIOUS_WORD_FINDER,
SettingsUtil.getValue(kitClass, Settings.PREVIOUS_WORD_FINDER,
new FinderFactory.PreviousWordBwdFinder(this, stopOnEOL, false)));
}
}
/** Create the syntax scanner for the document. The default
* behavior is to call the editor kit createSyntax() method.
*/
public Syntax createSyntax() {
return BaseKit.getKit(kitClass).createSyntax(this);
}
Syntax getFreeSyntax() {
synchronized (syntaxList) {
int cnt = syntaxList.size();
return (cnt > 0) ? (Syntax)syntaxList.remove(cnt - 1) : createSyntax();
}
}
void releaseSyntax(Syntax syntax) {
synchronized (syntaxList) {
syntaxList.add(syntax);
}
}
public SyntaxSupport getSyntaxSupport() {
if (syntaxSupport == null) {
syntaxSupport = BaseKit.getKit(kitClass).createSyntaxSupport(this);
}
return syntaxSupport;
}
/** Perform any generic text processing. The advantage of this method
* is that it allows the text to processed in line batches. The initial
* size of the batch is given by the Settings.LINE_BATCH_SIZE.
* The TextBatchProcessor.processTextBatch() method is called for every
* text batch. If the method returns true, it means the processing should
* continue with the next batch of text which will have double line count
* compared to the previous one. This guarantees there will be not too many
* batches so the processing should be more efficient.
* @param tbp text batch processor to be used to process the text batches
* @param startPos starting position of the processing.
* @param endPos ending position of the processing. This can be -1 to signal
* the end of document. If the endPos is lower than startPos then the batches
* are created in the backward direction.
* @return the returned value from the last tpb.processTextBatch() call.
* The -1 will be returned for (startPos == endPos).
*/
public int processText(TextBatchProcessor tbp, int startPos, int endPos)
throws BadLocationException {
if (endPos == -1) {
endPos = getLength();
}
int batchLineCnt = ((Integer)getProperty(Settings.LINE_BATCH_SIZE)).intValue();
int batchStart = startPos;
int ret = -1;
if (startPos < endPos) { // batching in forward direction
while (ret < 0 && batchStart < endPos) {
int batchEnd = Math.min(Utilities.getRowStart(this, batchStart, batchLineCnt), endPos);
if (batchEnd == -1) { // getRowStart() returned -1
batchEnd = endPos;
}
ret = tbp.processTextBatch(this, batchStart, batchEnd, (batchEnd == endPos));
batchLineCnt *= 2; // double the scanned area
batchStart = batchEnd;
}
} else {
while (ret < 0 && batchStart > endPos) {
int batchEnd = Math.max(Utilities.getRowStart(this, batchStart, -batchLineCnt), endPos);
ret = tbp.processTextBatch(this, batchStart, batchEnd, (batchEnd == endPos));
batchLineCnt *= 2; // double the scanned area
batchStart = batchEnd;
}
}
return ret;
}
public boolean isIdentifierPart(char ch) {
return identifierAcceptor.accept(ch);
}
public boolean isWhitespace(char ch) {
return whitespaceAcceptor.accept(ch);
}
/** Length of document.
* @return number of characters >= 0
*/
public final int getLength() {
return op.length();
}
/** Create the mark for the given position
* and store it in the list. The position can
* be later retrieved through its ID.
*/
int storePosition(int pos) throws BadLocationException {
Mark mark = op.insertMark(pos, false);
int ind;
if (posFreeList.size() > 0) {
ind = ((Integer)posFreeList.remove(posFreeList.size() - 1)).intValue();
posList.set(ind, mark);
} else { // no free indexes
ind = posList.size();
posList.add(mark);
}
return ind;
}
int getStoredPosition(int posID) {
if (posID < 0 || posID >= posList.size()) {
return -1;
}
try {
return ((Mark)posList.get(posID)).getOffset();
} catch (InvalidMarkException e) {
return -1;
}
}
void removeStoredPosition(int posID) {
if (posID >= 0 || posID < posList.size()) {
Mark mark = (Mark)posList.get(posID);
posList.set(posID, null); // clear the index
posFreeList.add(new Integer(posID));
}
}
/** Inserts string into document */
public void insertString(int offset, String text, AttributeSet a)
throws BadLocationException {
if (text == null || text.length() == 0) {
return;
}
// Check offset correctness
if (offset < 0 || offset > getLength()) {
throw new BadLocationException("Wrong insert position", offset); // NOI18N
}
// Check whether the insert is performed because of the input-methods.
// If so try to handle it through the defaultKeyTypedAction
/* if (a != null
&& a.getAttribute(javax.swing.text.StyleConstants.ComposedTextAttribute) != null
) {
javax.swing.text.JTextComponent c = BaseTextUI.getFocusedComponent();
if (c.getDocument() == this) { // additional check
BaseKit kit = Utilities.getKit(c);
if (kit != null) {
javax.swing.Action dkta = kit.getActionByName(
DefaultEditorKit.defaultKeyTypedAction);
if (dkta instanceof BaseKit.DefaultKeyTypedAction) {
((BaseKit.DefaultKeyTypedAction)dkta).actionPerformed(
new java.awt.event.ActionEvent(c,
java.awt.event.ActionEvent.ACTION_PERFORMED, text), c, true);
return;
}
}
}
}
*/
// possible CR-LF conversion
text = Analyzer.convertLSToLF(text);
// Perform the insert
try {
extWriteLock();
if (offset < 0 || offset > getLength()) {
throw new BadLocationException("Wrong insert position", offset); // NOI18N
}
preInsertUpdate(offset, text, a);
// Do the real insert into the content
UndoableEdit edit = op.insertString(offset, text);
BaseDocumentEvent evt = createDocumentEvent(offset, text.length(), DocumentEvent.EventType.INSERT);
if (edit != null) {
evt.addEdit(edit);
}
modified = true;
if (atomicDepth > 0) {
if (atomicEdits == null) {
atomicEdits = new CompoundEdit();
}
atomicEdits.addEdit(evt); // will be added
}
insertUpdate(evt, a);
evt.end();
fireInsertUpdate(evt);
boolean isComposedText = ((a != null)
&& (a.isDefined(StyleConstants.ComposedTextAttribute)));
if (atomicDepth == 0) { // !!! should handle the undo && !isComposedText) {
fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
}
} finally {
extWriteUnlock();
}
}
/** Removes portion of a document */
public void remove(int offset, int len) throws BadLocationException {
if (len > 0) {
try {
extWriteLock();
int docLen = getLength();
if (offset < 0 || offset > docLen) {
throw new BadLocationException("Wrong remove position", offset); // NOI18N
}
if (offset + len > docLen) {
throw new BadLocationException("End offset of removed text is too big", offset + len); // NOI18N
}
BaseDocumentEvent evt = createDocumentEvent(offset, len, DocumentEvent.EventType.REMOVE);
preRemoveUpdate(evt);
removeUpdate(evt);
UndoableEdit edit = op.remove(offset, len);
if (edit != null) {
evt.addEdit(edit);
}
if (atomicDepth > 0) { // add edits as soon as possible
if (atomicEdits == null) {
atomicEdits = new CompoundEdit();
}
atomicEdits.addEdit(evt); // will be added
}
postRemoveUpdate(evt);
evt.end();
fireRemoveUpdate(evt);
if (atomicDepth == 0) {
fireUndoableEditUpdate(new UndoableEditEvent(this, evt));
}
} finally {
extWriteUnlock();
}
}
}
/** This method is called automatically before the document
* insertion occurs and can be used to revoke the insertion before it occurs
* by throwing the <tt>BadLocationException</tt>.
* @param offset position where the insertion will be done
* @param text string to be inserted
* @param a attributes of the inserted text
*/
protected void preInsertUpdate(int offset, String text, AttributeSet a)
throws BadLocationException {
}
/** This method is called automatically before the document
* removal occurs and can be used to revoke the removal before it occurs
* by throwing the <tt>BadLocationException</tt>.
*/
protected void preRemoveUpdate(DefaultDocumentEvent evt)
throws BadLocationException {
}
public String getText(int[] block) throws BadLocationException {
return getText(block[0], block[1] - block[0]);
}
public char[] getChars(int pos, int len) throws BadLocationException {
return op.getChars(pos, len);
}
public char[] getChars(int[] block) throws BadLocationException {
return getChars(block[0], block[1] - block[0]);
}
public void getChars(int pos, char[] ret, int offset, int len)
throws BadLocationException {
op.getChars(pos, ret, offset, len);
}
/** Find something in document using a finder.
* @param finder finder to be used for the search
* @param startPos position in the document where the search will start
* @param limitPos position where the search will be end with reporting
* that nothing was found.
*/
public int find(Finder finder, int startPos, int limitPos)
throws BadLocationException {
if (finder instanceof AdjustFinder) {
int docLen = getLength();
if (limitPos == -1) {
limitPos = docLen;
}
if (startPos == -1) {
startPos = docLen;
}
if (startPos == limitPos) { // stop immediately
finder.reset(); // reset() should be called in all the cases
return -1; // must stop here because wouldn't know if fwd/bwd search?
}
boolean forward = (startPos < limitPos);
startPos = ((AdjustFinder)finder).adjustStartPos(this, startPos);
limitPos = ((AdjustFinder)finder).adjustLimitPos(this, limitPos);
boolean voidSearch = (forward ? (startPos >= limitPos) : (startPos <= limitPos));
if (voidSearch) {
finder.reset();
return -1;
}
}
return op.find(finder, startPos, limitPos);
}
public void print(PrintContainer container) {
try {
readLock();
ExtUI extUI = BaseKit.getKit(kitClass).createPrintExtUI(this);
Drawer.PrintDG printDG = new Drawer.PrintDG(container);
Drawer.getDrawer().draw(printDG, extUI, 0, getLength(), 0, 0, Integer.MAX_VALUE);
} catch (BadLocationException e) {
e.printStackTrace();
} finally {
readUnlock();
}
}
/** Create biased position in document */
public Position createPosition(int offset, Position.Bias bias)
throws BadLocationException {
return op.createPosition(offset, bias);
}
/** Return array of root elements - usually only one */
public Element[] getRootElements() {
Element[] elems = new Element[1];
elems[0] = getDefaultRootElement();
return elems;
}
/** Return default root element */
public Element getDefaultRootElement() {
if (defaultRootElem == null) {
defaultRootElem = new org.netbeans.editor.LeafElement(this, null, null, 0, getLength(), false, false);
}
return defaultRootElem;
}
/** Runs the runnable under read lock. */
public void render(Runnable r) {
try {
readLock();
r.run();
} finally {
readUnlock();
}
}
/** Runs the runnable under write lock. This is a stronger version
* of the runAtomicAsUser() method, because if there any locked sections
* in the documents this methods breaks the modification locks and modifies
* the document.
* If there are any excpeptions thrown during the processing of the runnable,
* all the document modifications are rolled back automatically.
*/
public void runAtomic(Runnable r) {
runAtomicAsUser(r);
}
/** Runs the runnable under write lock.
* If there are any excpeptions thrown during the processing of the runnable,
* all the document modifications are rolled back automatically.
*/
public void runAtomicAsUser(Runnable r) {
boolean completed = false;
try {
atomicLock();
r.run();
completed = true;
} finally {
try {
if (!completed) {
breakAtomicLock();
}
} finally {
atomicUnlock();
}
}
}
/** Insert contents of reader at specified position into document.
* @param reader reader from which data will be read
* @param pos on which position that data will be inserted
*/
public void read(Reader reader, int pos)
throws IOException, BadLocationException {
try {
extWriteLock();
if (pos < 0 || pos > getLength()) {
throw new BadLocationException("BaseDocument.read()", pos); // NOI18N
}
if (inited || modified) { // was the document already initialized?
Analyzer.read(this, reader, pos);
} else { // not initialized yet, we can use initialRead()
Analyzer.initialRead(this, reader, true);
BaseDocumentEvent evt = createDocumentEvent(0, 0, DocumentEvent.EventType.INSERT);
evt.end();
fireInsertUpdate(evt); // fire the insert event with zero length to notify about the change
inited = true; // initialized but not modified
}
} finally {
extWriteUnlock();
}
}
/** Write part of the document into specified writer.
* @param writer writer into which data will be written.
* @param pos from which position get the data
* @param len how many characters write
*/
public void write(Writer writer, int pos, int len)
throws IOException, BadLocationException {
try {
readLock();
if ((pos < 0) || ((pos + len) > getLength())) {
readUnlock();
throw new BadLocationException("BaseDocument.write()", pos); // NOI18N
}
Analyzer.write(this, writer, pos, len);
writer.flush();
} finally {
readUnlock();
}
}
/** Get tab size for this document. */
public int getTabSize() {
Integer tabSize = (Integer)getProperty(Settings.TAB_SIZE);
return (tabSize != null) ? tabSize.intValue() : 8;
}
public final Class getKitClass() {
return kitClass;
}
/** Extended write locking of the document allowing
* reentrant write lock acquiring.
*/
public synchronized final void extWriteLock() {
if (Thread.currentThread() != getCurrentWriter()) {
super.writeLock();
} else { // inner locking block
writeDeep++; // only increase write deepness
}
}
/** Extended write unlocking.
* @see extWriteLock()
*/
public synchronized final void extWriteUnlock() {
if (Thread.currentThread() != getCurrentWriter()) {
throw new Error(WRITE_LOCK_MISSING);
}
if (writeDeep == 0) { // most outer locking block
super.writeUnlock();
} else { // just inner locking block
writeDeep--;
}
}
public synchronized final void atomicLock() {
extWriteLock();
atomicDepth++;
}
public synchronized final void atomicUnlock() {
extWriteUnlock();
if (atomicDepth == 0) {
return;
}
if (--atomicDepth == 0) { // must fire possible undo event
if (atomicEdits != null) {
atomicEdits.end();
fireUndoableEditUpdate(new UndoableEditEvent(this, atomicEdits));
atomicEdits = null;
}
}
}
/** Is the document currently atomically locked?
* It's not synced as this method must be called only from writer thread.
*/
public final boolean isAtomicLock() {
return (atomicDepth > 0);
}
/** Break the atomic lock so that doc is no longer in atomic mode.
* All the performed changes are rolled back automatically.
* Even after calling this method, the atomicUnlock() must still be called.
* This method is not synced as it must be called only from writer thread.
*/
public final void breakAtomicLock() {
atomicDepth = 0;
if (atomicEdits != null) {
atomicEdits.end();
atomicEdits.undo();
atomicEdits = null;
}
}
protected final int getAtomicDepth() {
return atomicDepth;
}
protected BaseDocumentEvent createDocumentEvent(int pos, int length,
DocumentEvent.EventType type) {
return new BaseDocumentEvent(this, pos, length, type);
}
/** Was the document modified by either insert/remove
* but not the initial read)?
*/
public boolean isModified() {
return modified;
}
/** Get the layer with the specified name */
public DrawLayer findLayer(String layerName) {
return drawLayerList.findLayer(layerName);
}
public boolean addLayer(DrawLayer layer) {
if (drawLayerList.add(layer)) {
BaseDocumentEvent evt = createDocumentEvent(0, 0, DocumentEvent.EventType.CHANGE);
evt.addEdit(new BaseDocumentEvent.DrawLayerChange(layer.getName()));
fireChangedUpdate(evt);
return true;
} else {
return false;
}
}
final DrawLayerList getDrawLayerList() {
return drawLayerList;
}
/** Toggle the bookmark for the current line */
public boolean toggleBookmark(int pos) throws BadLocationException {
pos = Utilities.getRowStart(this, pos);
boolean marked = bookmarkChain.toggleMark(pos);
fireChangedUpdate(createDocumentEvent(pos, 0, DocumentEvent.EventType.CHANGE));
return marked;
}
/** Get the position of the next bookmark.
* @pos position from which to search
* @wrap wrap around the end of document
* @return position of the next bookmark or -1 if there is no mark
*/
public int getNextBookmark(int pos, boolean wrap)
throws BadLocationException {
try {
pos = Utilities.getRowStart(this, pos);
int rel = bookmarkChain.compareMark(pos);
MarkFactory.ChainDrawMark mark = bookmarkChain.getCurMark();
if (rel <= 0) { // right at this line, go next
if (mark != null) {
if (mark.next != null) {
return mark.next.getOffset();
} else { // last bookmark
return (wrap && bookmarkChain.chain != null) ?
bookmarkChain.chain.getOffset() : -1;
}
} else { // no marks
return -1;
}
} else { // mark after pos
return mark.getOffset();
}
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
return 0;
}
}
private LineRootElement getLineRootElement() {
if (lineRootElement == null) {
lineRootElement = new LineRootElement();
}
return lineRootElement;
}
public Element getParagraphElement(int pos) {
return getLineRootElement().getElement(
getLineRootElement().getElementIndex(pos));
}
/** Substitution for (each line = element) behavior */
class LineRootElement implements Element {
public Document getDocument() {
return BaseDocument.this;
}
public Element getParentElement() {
return null;
}
public String getName() {
return "line-elements-root"; // NOI18N
}
public AttributeSet getAttributes() {
return null;
}
public int getStartOffset() {
return 0;
}
public int getEndOffset() {
return getLength();
}
public int getElementIndex(int offset) {
try {
return Utilities.getLineOffset(BaseDocument.this, offset);
} catch (BadLocationException e) {
return 0;
}
}
public int getElementCount() {
return Utilities.getLineCount(BaseDocument.this);
}
public Element getElement(int index) {
if (index < 0) {
return null;
}
try {
int startPos = Utilities.getRowStartFromLineOffset(BaseDocument.this, index);
LineElement elem = null;
if (startPos >= 0) {
MarkFactory.LineMark mark = (MarkFactory.LineMark)op.getOffsetMark(
startPos, MarkFactory.LineMark.class);
if (mark == null || (LineElement)mark.lineElemRef.get() == null) {
mark = new MarkFactory.LineMark();
try {
op.insertMark(mark, startPos);
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
elem = new LineElement(mark);
mark.lineElemRef = new WeakReference(elem);
} else {
return (LineElement)mark.lineElemRef.get();
}
}
return elem;
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
return null;
}
}
public boolean isLeaf() {
return false;
}
}
/** Line element representation */
class LineElement implements Element {
/** Mark at the begining of the line */
MarkFactory.LineMark startMark;
LineElement(MarkFactory.LineMark startMark) throws BadLocationException {
this.startMark = startMark;
}
public Document getDocument() {
return BaseDocument.this;
}
public int getStartOffset() {
try {
return startMark.getOffset();
} catch (InvalidMarkException e) {
return 0;
}
}
public int getEndOffset() {
try {
return op.getEOLNL(getStartOffset());
} catch (BadLocationException e) {
return 0;
}
}
public Element getParentElement() {
return lineRootElement;
}
public String getName() {
return "line-element"; // NOI18N
}
public AttributeSet getAttributes() {
return null;
}
public int getElementIndex(int offset) {
return 0;
}
public int getElementCount() {
return 0;
}
public Element getElement(int index) {
return null;
}
public boolean isLeaf() {
return true;
}
public void finalize() throws Throwable {
try {
startMark.remove();
} catch (InvalidMarkException e) {
}
super.finalize();
}
public String toString() {
return "getStartOffset()=" + getStartOffset() // NOI18N
+ ", getEndOffset()=" + getEndOffset() // NOI18N
+ ", getParentElement()=" + getParentElement(); // NOI18N
}
}
}
/*
* Log
* 61 Gandalf-post-FCS1.54.1.5 4/13/00 Miloslav Metelka fixing remove from
* guarded blocks
* 60 Gandalf-post-FCS1.54.1.4 4/6/00 Miloslav Metelka undo for composed text
* 59 Gandalf-post-FCS1.54.1.3 4/5/00 Miloslav Metelka removed IM patch
* 58 Gandalf-post-FCS1.54.1.2 4/3/00 Miloslav Metelka undo update
* 57 Gandalf-post-FCS1.54.1.1 3/9/00 Miloslav Metelka tabsize fix
* 56 Gandalf-post-FCS1.54.1.0 3/8/00 Miloslav Metelka
* 55 Gandalf 1.54 1/19/00 Miloslav Metelka component in
* ActionEvent()
* 54 Gandalf 1.53 1/16/00 Miloslav Metelka
* 53 Gandalf 1.52 1/13/00 Miloslav Metelka
* 52 Gandalf 1.51 1/10/00 Miloslav Metelka
* 51 Gandalf 1.50 1/7/00 Miloslav Metelka
* 50 Gandalf 1.49 1/6/00 Miloslav Metelka
* 49 Gandalf 1.48 1/6/00 Miloslav Metelka
* 48 Gandalf 1.47 1/6/00 Miloslav Metelka
* 47 Gandalf 1.46 1/4/00 Miloslav Metelka
* 46 Gandalf 1.45 12/28/99 Miloslav Metelka
* 45 Gandalf 1.44 11/14/99 Miloslav Metelka
* 44 Gandalf 1.43 11/8/99 Miloslav Metelka
* 43 Gandalf 1.42 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 42 Gandalf 1.41 10/10/99 Miloslav Metelka
* 41 Gandalf 1.40 10/4/99 Miloslav Metelka
* 40 Gandalf 1.39 9/16/99 Miloslav Metelka
* 39 Gandalf 1.38 9/15/99 Miloslav Metelka
* 38 Gandalf 1.37 9/10/99 Miloslav Metelka
* 37 Gandalf 1.36 8/27/99 Miloslav Metelka
* 36 Gandalf 1.35 8/17/99 Miloslav Metelka
* 35 Gandalf 1.34 8/9/99 Ian Formanek Generated Serial Version
* UID
* 34 Gandalf 1.33 8/9/99 Miloslav Metelka undo/redo presentation
* names
* 33 Gandalf 1.32 7/30/99 Miloslav Metelka
* 32 Gandalf 1.31 7/29/99 Miloslav Metelka
* 31 Gandalf 1.30 7/26/99 Miloslav Metelka
* 30 Gandalf 1.29 7/20/99 Miloslav Metelka
* 29 Gandalf 1.28 7/9/99 Miloslav Metelka
* 28 Gandalf 1.27 7/2/99 Miloslav Metelka
* 27 Gandalf 1.26 6/29/99 Miloslav Metelka Scrolling and patches
* 26 Gandalf 1.25 6/25/99 Miloslav Metelka from floats back to ints
* 25 Gandalf 1.24 6/1/99 Miloslav Metelka
* 24 Gandalf 1.23 5/24/99 Miloslav Metelka
* 23 Gandalf 1.22 5/15/99 Miloslav Metelka fixes
* 22 Gandalf 1.21 5/13/99 Miloslav Metelka
* 21 Gandalf 1.20 5/10/99 Miloslav Metelka fix - line elem. mark
* 20 Gandalf 1.19 5/10/99 Miloslav Metelka bugfix
* 19 Gandalf 1.18 5/7/99 Miloslav Metelka line numbering and fixes
* 18 Gandalf 1.17 5/5/99 Miloslav Metelka
* 17 Gandalf 1.16 5/5/99 Miloslav Metelka
* 16 Gandalf 1.15 5/1/99 Petr Hamernik bugfix
* 15 Gandalf 1.14 4/23/99 Miloslav Metelka patched DocumentEvent
* 14 Gandalf 1.13 4/23/99 Miloslav Metelka changes in settings
* 13 Gandalf 1.12 4/23/99 Miloslav Metelka Undo added and internal
* improvements
* 12 Gandalf 1.11 4/8/99 Miloslav Metelka
* 11 Gandalf 1.10 4/1/99 Miloslav Metelka
* 10 Gandalf 1.9 3/30/99 Miloslav Metelka
* 9 Gandalf 1.8 3/27/99 Miloslav Metelka
* 8 Gandalf 1.7 3/23/99 Miloslav Metelka
* 7 Gandalf 1.6 3/18/99 Miloslav Metelka
* 6 Gandalf 1.5 3/18/99 Miloslav Metelka
* 5 Gandalf 1.4 3/18/99 Miloslav Metelka
* 4 Gandalf 1.3 3/18/99 Miloslav Metelka
* 3 Gandalf 1.2 2/13/99 Miloslav Metelka
* 2 Gandalf 1.1 2/9/99 Miloslav Metelka
* 1 Gandalf 1.0 2/3/99 Miloslav Metelka
* $
*/